let fs = require("fs");
let path = require("path");
let babylon = require("babylon");
let types = require("@babel/types");
let traverse = require("@babel/traverse").default;
let generator = require("@babel/generator").default;
let ejs = require("ejs");
let tapable = require("tapable");

class Compiler {
  constructor(config) {
    this.config = config;
    // 入口路径
    this.entryId;
    // 保存所有模块依赖
    this.modules = {};
    // 入口
    this.entry = config.entry;
    // 根路径
    this.root = process.cwd();
    this.hooks = {
      entryOption: new tapable.SyncHook(),
      compile: new tapable.SyncHook(),
      afterCompile: new tapable.SyncHook(),
      afterPlugins: new tapable.SyncHook(),
      run: new tapable.SyncHook(),
      emit: new tapable.SyncHook(),
      done: new tapable.SyncHook(),
    };
    let plugins = this.config.plugins;
    if (Array.isArray(plugins)) {
      plugins.forEach((plugin) => {
        plugin.apply(this);
      });
    }
  }
  run() {
    this.hooks.run.call();
    this.hooks.compile.call();
    this.buildModule(path.resolve(this.root, this.entry), true);
    this.hooks.afterCompile.call();
    this.hooks.afterPlugins.call();
    // console.log(this.entryId, this.modules);
    this.emitFile();
    this.hooks.emit.call();
    this.hooks.done.call();
  }
  getSource(modulePath) {
    let rules = this.config.module.rules;
    let content = fs.readFileSync(modulePath, "utf8");
    for (let i = 0; i < rules.length; i++) {
      let rule = rules[i];
      let { test, use } = rule;
      let len = use.length - 1;
      if (test.test(modulePath)) {
        function normalLoader() {
          let loader = require(use[len--]);
          content = loader(content);
          if (len >= 0) {
            normalLoader();
          }
        }
        normalLoader();
      }
    }
    return content;
  }
  //   构建模块
  buildModule(modulePath, isEntry) {
    let source = this.getSource(modulePath);
    // moduleName是文件的相对路径,可以通过path.relative计算
    let moduleName = "./" + path.relative(this.root, modulePath);
    if (isEntry) {
      this.entryId = moduleName;
    }
    let { sourceCode, dependencies } = this.parse(
      source,
      path.dirname(moduleName)
    );
    // console.log(sourceCode, dependencies);
    this.modules[moduleName] = sourceCode;
    dependencies.forEach((dep) => {
      this.buildModule(path.join(this.root, dep), false);
    });
  }
  //   解析源码
  parse(source, parentPath) {
    // https://astexplorer.net/   解析require("./a.js")
    let ast = babylon.parse(source);
    let dependencies = [];
    traverse(ast, {
      CallExpression(p) {
        let node = p.node;
        if (node.callee.name === "require") {
          node.callee.name = "__webpack_require__";
          let moduleName = node.arguments[0].value;
          moduleName = moduleName + (path.extname(moduleName) ? "" : ".js");
          moduleName = "./" + path.join(parentPath, moduleName);
          dependencies.push(moduleName);
          node.arguments = [types.stringLiteral(moduleName)];
        }
      },
    });
    let sourceCode = generator(ast).code;
    return { sourceCode, dependencies };
  }
  // 发射文件
  emitFile() {
    let main = path.join(this.config.output.path, this.config.output.filename);
    let template = this.getSource(path.join(__dirname, "main.ejs"));
    let Code = ejs.render(template, {
      entryId: this.entryId,
      modules: this.modules,
    });
    this.assets = {};
    this.assets[main] = Code;
    // console.log(Code);
    fs.writeFileSync(main, this.assets[main], "utf8");
  }
}
module.exports = Compiler;
